/*CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
 C      cpu_sim.c
 C      Application Name: tcss422-project2
 C      Programmers Names: Evan Phillips, Lucas Cowell, Tony Butterfield
 C      Date created:   May 17, 2011
 C      Date Completed:
 C      Brief Description: simulated cpu.  counts the steps of the running process and reacts to
 C                  traps and interrupts by updating the PCB and calling scheduler.
 C
 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC*/
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include "definitions.h"
#include "process.h"
#include "scheduler.h"
#include "io_device.h"
#include "semaphore.h"
#include "cpu_sim.h"
#include "process_queue.h"
#include "../main.h"


/*=================================================================================================
 =  Comment block for function: cpu_init()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      Process cpu_init()
 =      Return value ranges: n/a
 =      Parameter value ranges: n/a
 =      Error conditions: N/a
 =  Initializes a cpu and calls scheduler to get the first process.
 =
 ==================================================================================================*/
CPU cpu_init()
{
    //Should the Process Queue get passed in or the top of the queue?

    // my thought is that the init should call scheduler to get the first process in
    // ready queue.  (We haven't defined the scheduler yet, but it should probably own
    // the ready queue.) - Tony
    int error = 0;

	CPU cpu = (CPU) malloc(sizeof(CPUStr));
	(*cpu).running_process = NULL;
	(*cpu).interrupt_controller = 0;
	//TODO: evaluate why we can't start at instruction 0
	(*cpu).program_counter = 0;

	cpu->kbd = iodev_init(IO_DEV_KBD_INTERRUPT, cpu, &error);
	cpu->dsk = iodev_init(IO_DEV_DSK_INTERRUPT, cpu, &error);
	cpu->vid = iodev_init(IO_DEV_VID_INTERRUPT, cpu, &error);

	cpu->sem = semaphore_init();
	cpu->scheduler = sched_init(cpu);

	//sched_getNextReady(cpu->scheduler);

	return cpu;
}

/*=================================================================================================
 =  Comment block for function: cpu_tick()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int cpu_tick(CPU)
 =      Return value ranges: 0 on normal return, else error code
 =      Parameter value ranges: pointer to cpu
 =      Error conditions: NULL_POINTER_ERROR
 =  a tick signaling a cpu cycle. If the cpu detects traps/interrupts/exceptions, it will update the
 =  running process' PCB and call the scheduler to do a context switch.
 =
 ==================================================================================================*/
int cpu_tick(CPU cpu) {
    if (cpu == NULL) return NULL_POINTER_ERROR;
    int error = 0;

    if (cpu->running_process == NULL)       // first tick
    {
        sched_getNextReady(cpu->scheduler);
    }


    if (p_getPID(cpu->running_process, &error) == 0)
    {
        if (!p_queue_isEmpty(cpu->scheduler->ready_queue, &error))
        {
            p_queue_enqueue(cpu->scheduler->ready_queue, cpu->running_process);
            sched_getNextReady(cpu->scheduler);
        }
    }

    // checkKb(cpu);

    // check for interrupts and trap requests
    (*cpu).interrupt_controller |= p_getRequest(cpu->running_process, cpu->program_counter, &error);

    if (cpu->interrupt_controller != 0)
    {
        error |= cpu_saveState(cpu);
        error |= cpu_callScheduler(cpu);
    }

    cpu->program_counter = ++cpu->program_counter % cpu->running_process->num_steps;

    printf(".");
	return error;
}

/*=================================================================================================
 =  Comment block for function: cpu_saveState()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int cpu_saveState(CPU)
 =      Return value ranges: 0 on normal return, else error code
 =      Parameter value ranges: pointer to cpu
 =      Error conditions: NULL_POINTER_ERROR
 =  cpu saves current value of the program counter to the running process' PCB (p->pcb->next_step).
 =
 ==================================================================================================*/
int cpu_saveState(CPU cpu)
{
    if(cpu == NULL) return NULL_POINTER_ERROR;
    int error = 0;

    error |= p_setNextStep(cpu->running_process, cpu->program_counter + 1);
    error |= p_setState(cpu->running_process, P_INTERRUPTED);

    return error;
}

/*=================================================================================================
 =  Comment block for function: cpu_callScheduler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int cpu_callScheduler(CPU, Scheduler)
 =      Return value ranges: error code
 =      Parameter value ranges: pointer to cpu, pointer to scheduler
 =      Error conditions: NULL_POINTER_ERROR
 =  calls the scheduler to switch contexts.
 =
 ==================================================================================================*/
int cpu_callScheduler(CPU cpu)
{
    if(cpu == NULL) return NULL_POINTER_ERROR;

    int error = sched_processInterrupts(cpu->scheduler);

    return error;
}
